﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using NAccounting.Models;
using AutoMapper;
using System.Globalization;
using System.Threading;
using NAccounting.QueryObjects;

namespace NAccounting.Controllers
{
    [HandleError]
    [Authorize]
    public class LotController : Controller
    {
        private NAccountEntities db = new NAccountEntities();

        //
        // GET: /Lot/

        public ActionResult Index()
        {
            var model = new LotListModel();
            return View(model);
        }

        public ActionResult Create()
        {
            return Edit();
        }

        //
        // GET: /Lot/Edit/5

        public ActionResult Edit(int id = 0)
        {
            LotDetailModel model = null;
            Lot lot = db.Lots.SingleOrDefault(l => l.Id == id);

            if (lot == null)
            {
                model = new LotDetailModel 
                { 
                    CanSave = true,
                    CanSaveAndOpenLote = true,
                    PeriodStartJs = DateTime.Now.ToJsString(),
                    PeriodEndJs = DateTime.Now.ToJsString(),
                };

                return View(model);
            }

            model = Mapper.Map<Lot, LotDetailModel>(lot);
            return View(model);
        }

        //
        // POST: /Lot/Edit/5

        [HttpPost]
        public ActionResult Edit(LotDetailModel lot)
        {
            //TODO: Não deixar criar um lote para um período fechado.
            if (ModelState.IsValid)
            {
                //TODO: Rever quando for para abrir diretamente o lote
                var entity = Mapper.Map<LotDetailModel, Lot>(lot);

                if (!db.CompanyCloseDates.CanCreateLotInDate(entity.StartPeriod.FirstDayOfMonth()))
                {
                    this.ModelState.AddModelError("", "O período selecionado para o lote já encontra-se fechado!");
                    return View(lot);
                }
                if (entity.StartPeriod.Month != entity.EndPeriod.Month)
                {
                    this.ModelState.AddModelError("", "A data de início e fim precisam estar dentro do mesmo mês!");
                    return View(lot);
                }

                db.Lots.Attach(entity);
                db.ObjectStateManager.ChangeObjectState(entity, entity.Id > 0 ? EntityState.Modified : EntityState.Added);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(lot);
        }

        public ActionResult Close(int id)
        {
            try
            {
                var entity = db.Lots.SingleOrDefault(x => x.Id == id);
                if (entity == null) return HttpNotFound();

                entity.Close();

                return RedirectToAction("Index");
            }
            catch (Exception ex) {
                var errorInfo = new HandleErrorInfo(ex, "AccountEntry", "Save");
                return View("Error", errorInfo);
            }
        }

        public ActionResult ClosePeriod()
        {
            var accountingCompanyConfig = db.AccountingCompanyConfigs.FirstOrDefault();
            if (accountingCompanyConfig == null)
                throw new InvalidOperationException("Parâmetros de contabilidade não configurados para a empresa selecionada.");

            var startCloseDate = accountingCompanyConfig.StartDateForClosePeriod;
            var closedDates = db.CompanyCloseDates.Where(x => x.CloseDate >= startCloseDate).Select(x => x.CloseDate).ToList();
            var dates = new List<DateTime>();
            var currentDate = dates.FirstOrDefault();

            if (currentDate == default(DateTime))
                currentDate = startCloseDate;

            while (currentDate < DateTime.Now.Date)
            {
                if (!closedDates.Contains(currentDate))
                {
                    dates.Add(currentDate);
                }

                currentDate = currentDate.AddMonths(1);
            }

            var minDatePeriod = dates.Min(x => x);
            var maxDatePeriod = dates.Max(x => x).LastDayOfMonth();

            var openedLots = db.Lots.Where(x => x.StartPeriod >= minDatePeriod && x.EndPeriod <= maxDatePeriod && 
                                               !x.CloseDate.HasValue &&
                                               x.TotalDebit != x.TotalCredit).ToList();

            var model = new ClosePeriodsModel();

            foreach (var date in dates)
            {
                var lotsBydate = new ClosePeriodsModel.BalancedLotByDate
                {
                    Date = date.ToString("MMMM, yyyy").ToUpper(),
                    Lots = (from l in openedLots
                            where 
                                l.StartPeriod >= date && l.EndPeriod <= date.LastDayOfMonth()
                            select new NAccounting.Models.ClosePeriodsModel.BalancedLotDetail
                            {
                                Id = l.Id,
                                Description = l.Description,
                                Value = (l.TotalCredit - l.TotalDebit).ToCurrencyString(),
                                DivergenceType = l.TotalCredit > l.TotalDebit ? "D" : "C"
                            }).ToList()
                };

                model.LotsByDate.Add(lotsBydate);
            }

            return View(model);
        }

        [HttpPost]
        public ActionResult ClosePeriod(FormCollection form)
        {
            var selectedDate = form["selectedDate"];
            var dateToClose = DateTime.ParseExact(selectedDate.ToLower(), "MMMM, yyyy", System.Threading.Thread.CurrentThread.CurrentUICulture);

            if (db.CompanyCloseDates.Any(x => x.CloseDate == dateToClose))
            {
                return RedirectToAction("ClosePeriod");
            }

            var period = new TimePeriod(dateToClose.FirstDayOfMonth(), dateToClose.LastDayOfMonth().EndingOfDay());

            var lots = db.Lots.Where(x => x.StartPeriod >= period.Start && x.EndPeriod <= period.End).ToArray();

            if (lots.AnyUnbalanced())
            {
                return View("Error", new HandleErrorInfo(new InvalidOperationException(String.Format("Existem lotes desbalanceados para a data {0}", selectedDate)), "Lot", "CloseDate"));
            }

            lots.CloseAll();

            db.CompanyCloseDates.AddObject(new CompanyCloseDate { CompanyId = 1, CloseDate = period.Start });
            db.SaveChanges();
            return Redirect("ClosePeriod");
        }

        public ActionResult GetAllLots(string sidx, string sord, int page, int rows)
        {

            var stateOption = LotStateOption.Undefined;
            var queryObject = new LotQueryObject(db);
            if (Request.QueryString["LotStateOption"] != null && Request.QueryString["LotStateOption"] != "A")
            {
                stateOption = Request.QueryString["LotStateOption"] == "O" ? LotStateOption.Opened : LotStateOption.Closed;
            }

            if (Request.QueryString["DateOption"] != null && Request.QueryString["DateOption"] != "")
            {
                queryObject.Period = FilterDateOptions.GetPeriodByDateOption(Request.QueryString["DateOption"]);
            }

            queryObject.StateOption = stateOption;
            var pageIndex = page - 1;
            var pagedResult = queryObject.Get(pageIndex, rows);

            var totalRecords = pagedResult.Total;
            var totalPages = (int)Math.Ceiling((float)totalRecords / (float)rows);
            var lots = pagedResult.List;

            var culture = Thread.CurrentThread.CurrentUICulture;

            var jsonResult = new
            {
                totalPages = totalPages,
                page = page,
                records = totalRecords,
                rows = (
                    from l in lots
                    let cellValues = new { 
                        Id = l.Id.ToString(),
                        Actions = "",
                        Description = l.Description,
                        PeriodStart = l.StartPeriod,
                        PeriodEnd = l.EndPeriod,
                        TotalCredit = l.TotalCredit,
                        TotalDebit = l.TotalDebit,
                        OpenDate = l.OpenDate,
                        CloseDate = l.CloseDate,
                        canEditLot = Boolean.TrueString.ToLower()
                    }
                    select new
                    {
                        id = l.Id,
                        cell = new string[]{
                             cellValues.Id, 
                             cellValues.Actions, 
                             cellValues.Description, 
                             String.Format("{0} até {1}", cellValues.PeriodStart.ToString(culture.DateTimeFormat.ShortDatePattern), cellValues.PeriodEnd.ToString(culture.DateTimeFormat.ShortDatePattern)),
                             cellValues.TotalCredit.ToCurrencyString(),
                             cellValues.TotalDebit.ToCurrencyString(),
                             cellValues.OpenDate.ToStringForNullable(),
                             cellValues.CloseDate.ToStringForNullable(),
                             cellValues.canEditLot, 
                        }
                    }).ToArray()
            };

            return Json(jsonResult, JsonRequestBehavior.AllowGet);
        }

        public JsonResult CanCreateLotForDate(string date)
        {
            var dt = DateTime.Parse(date, System.Threading.Thread.CurrentThread.CurrentUICulture).FirstDayOfMonth().BeginningOfDay();
            var canCreateLotForDate = db.CompanyCloseDates.CanCreateLotInDate(dt);

            return Json(new { canCreateLotForDate = canCreateLotForDate }, JsonRequestBehavior.AllowGet);
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}